#include <Precomp.h>
#include <defs.h>
#include <player.h>
#include <string.h>
#include <packet.h>
#include <gameworld.h>
#include <EntityMaterialInstance.h>

using namespace Ogre;

CGalaxyStatus::CGalaxyStatus(CGameWorld* pGameWorld) : m_pGameWorld(pGameWorld), m_dwValidTeamMask(0xFFFFFFFF) { }

void CGalaxyStatus::HandleValidTeamMask(const mask_spacket* pPacket)
{
    m_dwValidTeamMask = pPacket->mask;
}

static void DeleteNodeAndEntity(SceneNode* pNode, Entity* pEntity)
{
    if(pEntity != NULL)
    {
        pNode->detachObject(pEntity);
        pNode->getCreator()->destroyEntity(pEntity);
    }

    if(pNode != NULL)
    {
        if(pNode->getParentSceneNode() != NULL)
        {
            pNode->getParentSceneNode()->removeChild(pNode);

        }
        pNode->getCreator()->destroySceneNode(pNode->getName());
    }
}


CGamePlayer::CGamePlayer(DWORD dwPlayerID, CGameWorld* pGameWorld) : 
    m_pGameWorld(pGameWorld), 
    m_dwPlayerID(dwPlayerID), 
    m_pPlayerEntity(NULL),
    m_pPlayerNode(NULL), 
    m_pShip(NULL),
    m_ePlayerStatus(ePlayerStatus_SlotFree),
    m_playerCloakHandler(this),
    m_playerTractorHandler(this, pGameWorld),
    m_dwFlags(0)
{ 
    m_szIdentifier[0] = ' ';

    if(dwPlayerID < 10)
    {
        m_szIdentifier[1] = '0' + ((char) dwPlayerID);
    }
    else
    {
        m_szIdentifier[1] = 'a' + ((char) dwPlayerID) - 10;
    }
    m_szIdentifier[2] = '\0';
    m_szName[0] = '\0';


    ostringstream playerNameStream;

    playerNameStream << "Player" << dwPlayerID;

    string playerName = playerNameStream.str();

    m_pPlayerNode = m_pGameWorld->GetSceneManager()->createSceneNode(playerName);


    playerNameStream << "Shield";
    string shieldName = playerNameStream.str();

    m_pPlayerShieldNode = m_pGameWorld->GetSceneManager()->createSceneNode(shieldName);


    m_pPlayerShieldEntity = m_pGameWorld->GetSceneManager()->createEntity(m_pPlayerShieldNode->getName(), "UnitSphere.mesh");
    m_pPlayerShieldEntity->setCastShadows(false);
    m_pPlayerShieldEntity->setMaterialName("shieldMaterial");
    m_pPlayerShieldNode->attachObject(m_pPlayerShieldEntity);
    m_pPlayerShieldNode->setScale(1.1f, 1.1f, 0.6f);

    m_fFirstPositionPacket = true;
}

CGamePlayer::~CGamePlayer()
{
    m_playerCloakHandler.Reset();

    DeleteNodeAndEntity(m_pPlayerShieldNode, m_pPlayerShieldEntity);
    m_pPlayerShieldNode = NULL;
    m_pPlayerShieldEntity = NULL;

    DeleteNodeAndEntity(m_pPlayerNode, m_pPlayerEntity);
    m_pPlayerNode = NULL;
    m_pPlayerEntity = NULL;
}

void CGamePlayer::HandlePlayerLogin(const plyr_login_spacket* pPacket)
{
    strcpy_s(m_szLogin, sizeof(m_szLogin), pPacket->login);
    strcpy_s(m_szName, sizeof(m_szName), pPacket->name);

    m_dwRank = pPacket->rank;
    m_dwPlayerID = pPacket->pnum;

    SendPlayerEvent(eGameWorldEventID_PlayerStatsUpdated);
}

void CGamePlayer::HandlePlayerInfo(const plyr_info_spacket* pPacket)
{
    EShipType eShipType = (EShipType) pPacket->shiptype;
    bool isRecreated = false;

    if(m_ePlayerStatus == ePlayerStatus_Alive)
    {
        isRecreated = true;
    }

    m_pShip = m_pGameWorld->GetShipByType(eShipType);

    m_eTeam = ETeamFlagToETeamID((ETeamFlags)pPacket->team);

    m_szIdentifier[0] = ETeamIDToString(m_eTeam)[0];

    SendPlayerEvent(eGameWorldEventID_PlayerStatsUpdated);

    if(isRecreated)
    {
        DestroyPlayerEntity();
        CreatePlayerEntity();
    }
}

void CGamePlayer::HandlePlayerKills(const kills_spacket* pPacket)
{
    m_flKills = (pPacket->kills * 1.0f) / 100.0f;

    SendPlayerEvent(eGameWorldEventID_PlayerStatsUpdated);
}

void CGamePlayer::HandlePlayerStats(const stats_spacket* pPacket)
{
    m_flMaxKills = (pPacket->maxkills * 1.0f) / 100.0f;
    m_flKills = (pPacket->kills * 1.0f) / 100.0f;
    m_dwLosses = pPacket->losses;
    m_dwArmiesBombed = pPacket->armies;
    m_dwPlanetsTaken = pPacket->planets;

    m_dwTournamentKills = pPacket->tkills;
    m_dwTournamentLosses = pPacket->tlosses;
    m_dwTournamentArmiesBombed = pPacket->tarmies;
    m_dwTournamentPlanetsTaken = pPacket->tplanets;
    m_dwTournamentTicks = pPacket->tticks;

    m_dwStarbaseKills = pPacket->sbkills;
    m_dwStarbaseLosses = pPacket->sblosses;
    m_flStarbaseMaxKills = (pPacket->sbmaxkills * 1.0f) / 100.0f;


    SendPlayerEvent(eGameWorldEventID_PlayerStatsUpdated);
}

void CGamePlayer::HandlePlayerStatus(const pstatus_spacket* pPacket)
{
    m_ePlayerStatus = (EPlayerStatus) pPacket->status;

    if(m_ePlayerStatus == ePlayerStatus_Alive)
    {
        CreatePlayerEntity();
        m_pGameWorld->GetSceneManager()->getRootSceneNode()->addChild(m_pPlayerNode);

        m_fFirstPositionPacket = true;

        SendPlayerEvent(eGameWorldEventID_PlayerIsAlive);
    }
    else
    {
        if(m_pPlayerNode->getParentSceneNode() != NULL)
        {
            SendPlayerEvent(eGameWorldEventID_PlayerExplodes);

            DestroyPlayerEntity();

            m_pGameWorld->GetSceneManager()->getRootSceneNode()->removeChild(m_pPlayerNode);
        }

        SendPlayerEvent(eGameWorldEventID_PlayerIsDead);
        m_fFirstPositionPacket = false;
    }

    SendPlayerEvent(eGameWorldEventID_PlayerStatsUpdated);
}

void CGamePlayer::CreatePlayerEntity()
{
    m_pPlayerEntity = m_pGameWorld->GetSceneManager()->createEntity(m_pPlayerNode->getName(), m_pGameWorld->GetShipMeshFileName(m_eTeam, m_pShip->GetShipType()));
    m_pPlayerEntity->setCastShadows(false);

    m_pPlayerNode->attachObject(m_pPlayerEntity);
    m_pPlayerNode->setScale(c_uiShipRadius * 1.0f, c_uiShipRadius * 1.0f, c_uiShipRadius * 1.0f);

    m_playerCloakHandler.PlayerAlive();
}


void CGamePlayer::DestroyPlayerEntity()
{
    m_playerCloakHandler.Reset();
    m_pPlayerNode->detachObject(m_pPlayerEntity);
    m_pGameWorld->GetSceneManager()->destroyEntity(m_pPlayerEntity);
    m_pPlayerEntity = NULL;
}


void CGamePlayer::HandlePlayerFlags(const flags_spacket* pPacket)
{
    DWORD dwFlagsOld = m_dwFlags;
    m_dwFlags = pPacket->flags;

    SendPlayerEvent(eGameWorldEventID_PlayerStatsUpdated);

    if(!(dwFlagsOld & ePlayerFlag_Cloaked) && (m_dwFlags & ePlayerFlag_Cloaked))
    {
        SendPlayerEvent(eGameWorldEventID_PlayerCloaked);
    }
    else if((dwFlagsOld & ePlayerFlag_Cloaked) && !(m_dwFlags & ePlayerFlag_Cloaked))
    {
        SendPlayerEvent(eGameWorldEventID_PlayerDecloaked);
    }
}

void CGamePlayer::HandlePlayerLocation(const player_spacket* pPacket)
{
    m_bPreviousHeading = m_bHeading;
    m_dwPreviousXLocation = m_dwXLocation;
    m_dwPreviousYLocation = m_dwYLocation;


    m_bHeading = pPacket->dir;
    m_bSpeed = pPacket->speed;
    m_dwXLocation = pPacket->x;                           
    m_dwYLocation = pPacket->y;

    if(m_fFirstPositionPacket)
    {
        m_bPreviousHeading = m_bHeading;
        m_dwPreviousXLocation = m_dwXLocation;
        m_dwPreviousYLocation = m_dwYLocation;
    }

    m_fFirstPositionPacket = false;

    if(m_ePlayerStatus == ePlayerStatus_Alive)
    {
        m_flTimeSinceLastUpdate = -m_pGameWorld->GetTimeSinceLastFrameAsFloat();
    }

    SendPlayerEvent(eGameWorldEventID_PlayerLocationUpdated);
}

void CGamePlayer::HandlePlayerHostilitySettings(const hostile_spacket* pPacket)
{
    m_dwHostile = pPacket->hostile;
    m_dwWar = pPacket->war;

    SendPlayerEvent(eGameWorldEventID_PlayerStatsUpdated);
}

void CGamePlayer::FrameUpdate()
{
    m_flTimeSinceLastUpdate += m_pGameWorld->GetTimeSinceLastFrameAsFloat();
    if(m_flTimeSinceLastUpdate > 0.1f && m_bSpeed == 0 && !(m_dwFlags & ePlayerFlag_Orbit))
    {
        m_dwPreviousXLocation = m_dwXLocation;
        m_dwPreviousYLocation = m_dwYLocation;
    }
// TODO - Lots more here. We need to be 'behind' the network update interval by some adaptive amount.
//
    Vector3 vectorVelocity = (Vector3(m_dwXLocation * 1.0f, m_dwYLocation * 1.0f, 0.0f) - 
                              Vector3(m_dwPreviousXLocation* 1.0f, m_dwPreviousYLocation * 1.0f, 0.0f)) / 0.1f;

    Vector3 vectorPosition = Vector3(m_dwXLocation * 1.0f, m_dwYLocation * 1.0f, 0.0f) + vectorVelocity * m_flTimeSinceLastUpdate;

    m_pPlayerNode->setPosition(vectorPosition.x, vectorPosition.y, 0.0f); // -1.0f * c_uiOrbitRadius * 0.25f

    m_pPlayerNode->resetOrientation();
    float flOrientation = m_bHeading;
    if(m_bPreviousHeading != m_bHeading)
    {
        float flInterpValue = m_flTimeSinceLastUpdate / 0.10f;
        if(flInterpValue > 1.0f)
        {
            flInterpValue = 1.0f;
        }
        float flDistLeft = 1.0f*(m_bHeading - m_bPreviousHeading);
        float flDistRight = 1.0f*(m_bPreviousHeading - m_bHeading);
        if(flDistLeft < 0.0f) flDistLeft += 256.0f;
        if(flDistRight < 0.0f) flDistRight += 256.0f;

        if(flDistLeft < flDistRight)
        {
            flOrientation = m_bPreviousHeading * 1.0f + flDistLeft * flInterpValue;
        }
        else
        {
            flOrientation = m_bPreviousHeading * 1.0f - flDistRight * flInterpValue;
        }
        if(flOrientation < 0.0f)
        {
            flOrientation += 256.0f;
        }
    }
    m_pPlayerNode->roll(Radian(Math::TWO_PI * flOrientation / 256.0f));

    if((m_dwFlags & ePlayerFlag_Shield) && (m_pGameWorld->GetPlayerForLocalPlayer() == this || !(m_dwFlags & ePlayerFlag_Cloaked)))
    {
        if(m_pPlayerShieldNode->getParent() == NULL)
        {
            m_pPlayerNode->addChild(m_pPlayerShieldNode);
        }
    }
    else
    {
        if(m_pPlayerShieldNode->getParent() != NULL)
        {
            m_pPlayerNode->removeChild(m_pPlayerShieldNode);
        }
    }

    if(m_dwFlags & ePlayerFlag_Cloaked)
    {
        if(m_pGameWorld->GetPlayerForLocalPlayer() == this)
        {
            m_playerCloakHandler.SetCloakState(0.80f);
        }
        else
        {
            m_playerCloakHandler.SetCloakState(1.0f);
        }
    }
    else
    {
        m_playerCloakHandler.SetCloakState(0.0f);
    }


    m_playerCloakHandler.Update(m_pGameWorld->GetTimeSinceLastFrameAsFloat());
    m_playerTractorHandler.Update(m_pGameWorld->GetTimeSinceLastFrameAsFloat());
}

void CGamePlayer::SendPlayerEvent(EGameWorldEventID eEventID)
{
    CPlayerEvent playerEvent(this, eEventID);
    m_pGameWorld->FireEvent(&playerEvent);
}
void CGamePlayer::BeginTractor() { SendPlayerEvent(eGameWorldEventID_TractorBegin); }
void CGamePlayer::EndTractor()   { SendPlayerEvent(eGameWorldEventID_TractorEnd); }

float CGamePlayer::GetRenderedXLocation() const
{
    return m_pPlayerNode->getPosition().x;
}
float CGamePlayer::GetRenderedYLocation() const
{
    return m_pPlayerNode->getPosition().y;
}

CLocalPlayer::CLocalPlayer(CGameWorld* pGameWorld) : m_pGameWorld(pGameWorld), m_dwPlayerID(0) { }

void CLocalPlayer::HandleYouPacket(const you_spacket* pPacket)
{
    m_dwPlayerID = pPacket->pnum;
    m_dwArmies = pPacket->armies;
    m_dwDamage = pPacket->damage;
    m_dwShield = pPacket->shield;
    m_dwEngineTemp = pPacket->etemp;
    m_dwWeaponTemp = pPacket->wtemp;

    m_dwFuel = pPacket->fuel;

    m_eKilledReason = (EKilledReason) pPacket->whydead;
    m_playerIDKilledBy = pPacket->whodead;


    // Propagate the flags as a flags packet - 
    flags_spacket flagsPacket;
    flagsPacket.flags = pPacket->flags;

    m_pGameWorld->GetPlayerForLocalPlayer()->HandlePlayerFlags(&flagsPacket);

    // char    hostile; // How isn't this handled earlier?
    // char    swar; ?? Sticky war?

}

CPlanet::CPlanet(DWORD dwPlanetID, CGameWorld* pGameWorld) : m_dwPlanetID(dwPlanetID), m_pGameWorld(pGameWorld), m_flRotation(0.0f), 
                                                             m_eTeamOwner(eTeamID_None), m_dwFlags(0), m_dwArmies(0), m_dwWhichTeamsHaveInfoBitfield(0)
{ 
    ostringstream planetNameStream;
    planetNameStream << "Planet" << dwPlanetID;
    string planetName = planetNameStream.str();

    ostringstream planetMaterialNameStream;
    planetMaterialNameStream << "planetMaterial" << ((dwPlanetID % 12) + 1);
    string planetMaterialName = planetMaterialNameStream.str();


    m_pPlanetEntity = m_pGameWorld->GetSceneManager()->createEntity(planetName, "UnitSphere.mesh");
    m_pPlanetEntity->setCastShadows(false);
    m_pPlanetEntity->setMaterialName(planetMaterialName);

    m_pPlanetNode = m_pGameWorld->GetSceneManager()->getRootSceneNode()->createChildSceneNode(planetName);
    m_pPlanetNode->attachObject(m_pPlanetEntity);

    m_pPlanetNode->setScale(c_uiPlanetRadius * 0.8f, c_uiPlanetRadius * 0.8f, c_uiPlanetRadius * 0.8f);
}

CPlanet::~CPlanet()
{
    if(m_pPlanetEntity != NULL)
    {
        m_pPlanetNode->detachObject(m_pPlanetEntity);
        m_pGameWorld->GetSceneManager()->destroyEntity(m_pPlanetEntity);
        m_pPlanetEntity = NULL;
    }

    if(m_pPlanetNode != NULL)
    {
        if(m_pPlanetNode->getParentSceneNode() != NULL)
        {
            m_pPlanetNode->getParentSceneNode()->removeChild(m_pPlanetNode);

        }
        m_pPlanetNode->getCreator()->destroySceneNode(m_pPlanetNode->getName());
        m_pPlanetNode = NULL;
    }
}

void CPlanet::HandlePlanetNameAndLocation(const planet_loc_spacket* pPacket)
{   
    m_dwPlanetID = pPacket->pnum;
    m_dwXLocation = pPacket->x;
    m_dwYLocation = pPacket->y;
    strcpy_s(m_szName, sizeof(m_szName), pPacket->name);

    m_pPlanetNode->setPosition(m_dwXLocation * 1.0f, m_dwYLocation * 1.0f, 0.0f);

    SendPlanetEvent(eGameWorldEventID_PlanetNameLocationChanged);
}

void CPlanet::HandlePlanetResourceInfo(const planet_spacket* pPacket)
{
    m_eTeamOwner = ETeamFlagToETeamID((ETeamFlags)pPacket->owner);
    m_dwFlags = pPacket->flags;
    m_dwArmies = pPacket->armies;
    m_dwWhichTeamsHaveInfoBitfield = pPacket->info;

    SendPlanetEvent(eGameWorldEventID_PlanetResourceUpdated);
}

void CPlanet::FrameUpdate()
{
    m_flRotation += m_pGameWorld->GetTimeSinceLastFrameAsFloat() / 6.0f;

    m_pPlanetNode->resetOrientation();
    m_pPlanetNode->roll(Radian(m_flRotation));
}

void CPlanet::SendPlanetEvent(EGameWorldEventID eEventID)
{
    CPlanetEvent planetEvent(this, eEventID);
    m_pGameWorld->FireEvent(&planetEvent);
}


CTorp::CTorp(DWORD dwTorpID, CGameWorld* pGameWorld) : m_dwTorpID(dwTorpID), m_pGameWorld(pGameWorld), m_pTorpBillboard(NULL), m_eTorpStatus(eTorpStatus_SlotFree)
{ 
}

CGamePlayer* CTorp::GetOwner()
{
    const int maxTorpDivisor = 8;

    return m_pGameWorld->GetPlayerByID(m_dwTorpID / maxTorpDivisor);
}

bool CTorp::IsInactiveTorpStatus(ETorpStatus eTorpStatus)
{
    if(eTorpStatus == eTorpStatus_SlotFree || eTorpStatus == eTorpStatus_Off || eTorpStatus == eTorpStatus_Detted || eTorpStatus == eTorpStatus_Exploding)
    {
        return true;
    }
    return false;
}

void CTorp::HandleTorpStatus(const torp_info_spacket* pPacket)
{
    ETorpStatus eTorpStatusOld = m_eTorpStatus;

    m_eTorpStatus = (ETorpStatus) pPacket->status;
    m_dwWarBitfield = pPacket->war;

    if(IsInactiveTorpStatus(eTorpStatusOld) && !IsInactiveTorpStatus(m_eTorpStatus))
    {
        m_pTorpBillboard = m_pGameWorld->GetTorpBillboardSet()->createBillboard(0.0f, 0.0f, 0.0f);
        m_pTorpBillboard->setTexcoordIndex((uint16)GetOwner()->GetTeamID());

        m_dwTorpSpeed = GetOwner()->GetShipData()->GetTorpSpeed();
        m_dwXLocation = GetOwner()->GetXLocation();
        m_dwYLocation = GetOwner()->GetYLocation();
        m_flTimeSinceLastUpdate = 0.0f;
        m_flTimeSinceZero = 0.0f;

        SendTorpEvent(eGameWorldEventID_TorpFired);
    }
    else if(!IsInactiveTorpStatus(eTorpStatusOld) && IsInactiveTorpStatus(m_eTorpStatus))
    {
        m_pGameWorld->GetTorpBillboardSet()->removeBillboard(m_pTorpBillboard);
        m_pTorpBillboard = NULL;
        if(m_eTorpStatus == eTorpStatus_Exploding)
        {
            SendTorpEvent(eGameWorldEventID_TorpHit);
        }
    }

}

void CTorp::HandleTorpLocation(const torp_spacket* pPacket)
{
    m_bHeading = CalculateHeading(pPacket->x, pPacket->y);

    m_dwXLocation = pPacket->x;
    m_dwYLocation = pPacket->y;

    if(m_pTorpBillboard != NULL)
    {
         m_pTorpBillboard->setPosition(m_dwXLocation * 1.0f, m_dwYLocation * 1.0f, 0.0f);
    }

    m_flTimeSinceLastUpdate = -m_pGameWorld->GetTimeSinceLastFrameAsFloat();
}

BYTE CTorp::CalculateHeading(DWORD dwNewX, DWORD dwNewY)
{
    Vector3 headingVector(dwNewX * 1.0f, dwNewY * 1.0f, 0.0f);

    headingVector -= Vector3(m_dwXLocation * 1.0f, m_dwYLocation * 1.0f, 0.0f);
    headingVector.normalise();
    // Dot product with negative y to get angle deviation from top dead center.
    float dotProduct = headingVector.dotProduct(Vector3(0.0f, -1.0f, 0.0f));

    bool isNegative = false;
    if(headingVector.x < 0.0f)
    {
        isNegative = true;
    }

    float flAngle = acos(dotProduct);

    if(isNegative)
    {
        flAngle = (Math::TWO_PI) - flAngle;
    }

    return (BYTE) (256.0f * flAngle / (2.0f * 3.14f));
}

void CTorp::FrameUpdate()
{
    if(m_eTorpStatus != eTorpStatus_Moving)
    {
        return;
    }

    m_flTimeSinceLastUpdate += m_pGameWorld->GetTimeSinceLastFrameAsFloat();
    m_flTimeSinceZero += m_pGameWorld->GetTimeSinceLastFrameAsFloat();


    m_pTorpBillboard->setRotation(Radian(-m_flTimeSinceZero));

    Matrix3 matrixRot;

    matrixRot.FromAxisAngle(Vector3::UNIT_Z, Radian( (Math::TWO_PI * m_bHeading) / 255.0f));
    Vector3 vectorMotion = matrixRot * (-1.0 * Vector3::UNIT_Y);
    Vector3 scaledVectorMotion = vectorMotion * (m_dwTorpSpeed * 200.0f) * m_flTimeSinceLastUpdate;

    m_pTorpBillboard->setPosition(m_dwXLocation * 1.0f + scaledVectorMotion.x, m_dwYLocation * 1.0f + scaledVectorMotion.y, 0.0f);
}

void CTorp::SendTorpEvent(EGameWorldEventID eGameWorldEventID)
{
    CTorpEvent torpEvent(this, eGameWorldEventID);
    m_pGameWorld->FireEvent(&torpEvent);
}


CTorp::~CTorp()
{
    if(m_pTorpBillboard != NULL)
    {
        m_pGameWorld->GetTorpBillboardSet()->removeBillboard(m_pTorpBillboard);
        m_pTorpBillboard = NULL;
    }
}



CPlasma::CPlasma(DWORD dwPlasmaID, CGameWorld* pGameWorld) : m_dwPlasmaID(dwPlasmaID), m_pGameWorld(pGameWorld), m_ePlasmaStatus(ePlasmaStatus_SlotFree), m_pPlasmaBillboard(NULL) { }

void CPlasma::HandlePlasmaStatus(const plasma_info_spacket* pPacket)
{
    EPlasmaStatus ePlasmaStatusOld = m_ePlasmaStatus;

    m_ePlasmaStatus = (EPlasmaStatus) pPacket->status;
    m_dwWarBitfield = pPacket->war;

    if(IsInactivePlasmaStatus(ePlasmaStatusOld) && !IsInactivePlasmaStatus(m_ePlasmaStatus))
    {
        m_pPlasmaBillboard = m_pGameWorld->GetPlasmaBillboardSet()->createBillboard(0.0f, 0.0f, 0.0f);
        m_pPlasmaBillboard->setTexcoordIndex((uint16)GetOwner()->GetTeamID());

        m_dwPlasmaSpeed = GetOwner()->GetShipData()->GetTorpSpeed();
        m_dwXLocation = GetOwner()->GetXLocation();
        m_dwYLocation = GetOwner()->GetYLocation();

        m_flTimeSinceLastUpdate = 0.0f;
        m_flTimeSinceZero = 0.0f;

        SendPlasmaEvent(eGameWorldEventID_PlasmaFired);
    }
    else if(!IsInactivePlasmaStatus(ePlasmaStatusOld) && IsInactivePlasmaStatus(m_ePlasmaStatus))
    {
        m_pGameWorld->GetPlasmaBillboardSet()->removeBillboard(m_pPlasmaBillboard);
        m_pPlasmaBillboard = NULL;
        if(m_ePlasmaStatus == eTorpStatus_Exploding)
        {
            SendPlasmaEvent(eGameWorldEventID_PlasmaHit);
        }
    }
}

void CPlasma::HandlePlasmaLocation(const plasma_spacket* pPacket)
{
    m_bHeading = CalculateHeading(pPacket->x, pPacket->y);

    m_dwXLocation = pPacket->x;
    m_dwYLocation = pPacket->y;

    if(m_pPlasmaBillboard != NULL)
    {
         m_pPlasmaBillboard->setPosition(m_dwXLocation * 1.0f, m_dwYLocation * 1.0f, 0.0f);
    }

    m_flTimeSinceLastUpdate = -m_pGameWorld->GetTimeSinceLastFrameAsFloat();
}

void CPlasma::FrameUpdate()
{
    if(m_ePlasmaStatus != eTorpStatus_Moving)
    {
        return;
    }

    m_flTimeSinceLastUpdate += m_pGameWorld->GetTimeSinceLastFrameAsFloat();
    m_flTimeSinceZero += m_pGameWorld->GetTimeSinceLastFrameAsFloat();


    m_pPlasmaBillboard->setRotation(Radian(-m_flTimeSinceZero));

    Matrix3 matrixRot;

    matrixRot.FromAxisAngle(Vector3::UNIT_Z, Radian( (Math::TWO_PI * m_bHeading) / 255.0f));
    Vector3 vectorMotion = matrixRot * (-1.0 * Vector3::UNIT_Y);
    Vector3 scaledVectorMotion = vectorMotion * (m_dwPlasmaSpeed * 200.0f) * m_flTimeSinceLastUpdate;

    m_pPlasmaBillboard->setPosition(m_dwXLocation * 1.0f + scaledVectorMotion.x, m_dwYLocation * 1.0f + scaledVectorMotion.y, 0.0f);
}

CGamePlayer* CPlasma::GetOwner()
{
    const int maxPlasmaDivisor = 1;

    return m_pGameWorld->GetPlayerByID(m_dwPlasmaID / maxPlasmaDivisor);
}


void CPlasma::SendPlasmaEvent(EGameWorldEventID eGameWorldEventID)
{
    CPlasmaEvent plasmaEvent(this, eGameWorldEventID);
    m_pGameWorld->FireEvent(&plasmaEvent);
}

bool CPlasma::IsInactivePlasmaStatus(EPlasmaStatus ePlasmaStatus)
{
    if(ePlasmaStatus == ePlasmaStatus_SlotFree || ePlasmaStatus == ePlasmaStatus_Detted || ePlasmaStatus == ePlasmaStatus_Exploding)
    {
        return true;
    }
    return false;
}


BYTE CPlasma::CalculateHeading(DWORD dwNewX, DWORD dwNewY)
{
    Vector3 headingVector(dwNewX * 1.0f, dwNewY * 1.0f, 0.0f);

    headingVector -= Vector3(m_dwXLocation * 1.0f, m_dwYLocation * 1.0f, 0.0f);
    headingVector.normalise();
    // Dot product with negative y to get angle deviation from top dead center.
    float dotProduct = headingVector.dotProduct(Vector3(0.0f, -1.0f, 0.0f));

    bool isNegative = false;
    if(headingVector.x < 0.0f)
    {
        isNegative = true;
    }

    float flAngle = acos(dotProduct);

    if(isNegative)
    {
        flAngle = (Math::TWO_PI) - flAngle;
    }

    return (BYTE) (256.0f * flAngle / (2.0f * 3.14f));
}



CPhaser::CPhaser(DWORD dwPhaserID, CGameWorld* pGameWorld) : m_dwPhaserID(dwPhaserID), m_pGameWorld(pGameWorld), m_pPhaserBillboard(NULL), m_ePhaserStatus(ePhaserStatus_SlotFree)
{ 
    ostringstream phaserNameStream;

    phaserNameStream << "Phaser" << dwPhaserID;

}

void CPhaser::HandlePhaser(const phaser_spacket* pPacket)
{
    EPhaserStatus ePhaserStatusOld = m_ePhaserStatus;

    m_bHeading = pPacket->dir;
    m_ePhaserStatus = (EPhaserStatus) pPacket->status;
    m_dwPlayerIDTarget = pPacket->target;
    m_dwXLocation = pPacket->x;
    m_dwYLocation = pPacket->y;

    // Phaser ID and player ID should map 1 to 1.

    if(ePhaserStatusOld == ePhaserStatus_SlotFree && m_ePhaserStatus != ePhaserStatus_SlotFree)
    {
        m_pPhaserBillboard = m_pGameWorld->GetPhaserBillboardSet()->createBillboard(0.0f, 0.0f, 0.0f);
        m_pPhaserBillboard->setTexcoordIndex((uint16)GetOwner()->GetTeamID());
        m_flElongateTime = 0.0f;

        SendPhaserEvent(eGameWorldEventID_PhaserFired);
    }
    else if(ePhaserStatusOld != ePhaserStatus_SlotFree && m_ePhaserStatus == ePhaserStatus_SlotFree)
    {
        m_pGameWorld->GetPhaserBillboardSet()->removeBillboard(m_pPhaserBillboard);
        m_pPhaserBillboard = NULL;
    }
}

CPhaser::~CPhaser()
{
    if(m_pPhaserBillboard != NULL)
    {
        m_pGameWorld->GetPhaserBillboardSet()->removeBillboard(m_pPhaserBillboard);
        m_pPhaserBillboard = NULL;
    }
}

void CPhaser::FrameUpdate()
{
    if(m_ePhaserStatus == ePhaserStatus_SlotFree)
    {
        return;
    }

    CGamePlayer* pPlayer = GetOwner();
    float flDistance = 0.0f;
    Vector3 vectorFiring;

    if(m_ePhaserStatus == ePhaserStatus_HitPlayer)
    {
        CGamePlayer* pPlayerHit = m_pGameWorld->GetPlayerByID(m_dwPlayerIDTarget);

        vectorFiring = Vector3(pPlayerHit->GetRenderedXLocation(), pPlayerHit->GetRenderedYLocation(), 0.0f) - 
            Vector3(pPlayer->GetRenderedXLocation(), pPlayer->GetRenderedYLocation(), 0.0f);

        flDistance = vectorFiring.length();
    }
    else
    {
        Matrix3 matrixRot;

        matrixRot.FromAxisAngle(Vector3::UNIT_Z, Radian( (Math::TWO_PI * m_bHeading) / 255.0f));
        vectorFiring = matrixRot * (-1.0 * Vector3::UNIT_Y);
        flDistance = 10000.0f;
    }

    vectorFiring.normalise();

    float flMaxElongateTime = 0.30f;
    m_flElongateTime += m_pGameWorld->GetTimeSinceLastFrameAsFloat();
    if(m_flElongateTime > flMaxElongateTime)
    {
        m_flElongateTime = flMaxElongateTime;
    }

    float flActualDistance = flDistance * (m_flElongateTime / flMaxElongateTime);

    if(m_pPhaserBillboard != NULL)
    {
        Vector3 vectorOffset = (vectorFiring * flActualDistance / 2.0f) + Vector3(pPlayer->GetRenderedXLocation(), pPlayer->GetRenderedYLocation(), 0.0f);

        m_pPhaserBillboard->setDimensions(120.0f, flActualDistance);

        m_pPhaserBillboard->mDirection = vectorFiring;
        m_pPhaserBillboard->setPosition(vectorOffset.x, vectorOffset.y, 0.0f);
    }

}

CGamePlayer* CPhaser::GetOwner()
{
    return m_pGameWorld->GetPlayerByID(m_dwPhaserID);
}


void CPhaser::SendPhaserEvent(EGameWorldEventID eGameWorldEventID)
{
    CPhaserEvent phaserEvent(this, eGameWorldEventID);
    m_pGameWorld->FireEvent(&phaserEvent);
}



// TODO - fileify these
CShipData::CShipData(EShipType eShipType)
{
    m_eShipType = eShipType;

    if(eShipType == eShipType_Scout)
    {
        m_wPhaserDamage     = 75;
        m_dwMaxSpeed        = 12;
        m_dwMaxFuel         = 5000;
        m_dwMaxShield       = 75;
        m_dwMaxDamage       = 75;
        m_dwMaxEngineTemp   = 1000;
        m_dwMaxWeaponTemp   = 1000;
        m_wMaxArmies        = 2;
        m_wWidth            = 20;
        m_wHeight           = 20;
        m_dwTorpSpeed       = 16;
    }
    else if(eShipType == eShipType_Destroyer)
    {
        m_wPhaserDamage     = 85;
        m_dwMaxSpeed        = 10;
        m_dwMaxFuel         = 7000;
        m_dwMaxShield       = 85;
        m_dwMaxDamage       = 85;
        m_dwMaxEngineTemp   = 1000;
        m_dwMaxWeaponTemp   = 1000;
        m_wMaxArmies        = 5;
        m_wWidth            = 20;
        m_wHeight           = 20;
        m_dwTorpSpeed       = 14;
    }
    else if(eShipType == eShipType_Battleship)
    {
        m_wPhaserDamage     = 105;
        m_dwMaxSpeed        = 8;
        m_dwMaxFuel         = 14000;
        m_dwMaxShield       = 130;
        m_dwMaxDamage       = 130;
        m_dwMaxEngineTemp   = 1000;
        m_dwMaxWeaponTemp   = 1000;
        m_wMaxArmies        = 6;
        m_wWidth            = 20;
        m_wHeight           = 20;
        m_dwTorpSpeed       = 12;
    }
    else if(eShipType == eShipType_Assault)
    {
        m_wPhaserDamage     = 80;
        m_dwMaxSpeed        = 8;
        m_dwMaxFuel         = 6000;
        m_dwMaxShield       = 80;
        m_dwMaxDamage       = 200;
        m_dwMaxEngineTemp   = 1200;
        m_dwMaxWeaponTemp   = 1000;
        m_wMaxArmies        = 20;
        m_wWidth            = 20;
        m_wHeight           = 20;
        m_dwTorpSpeed       = 16;
    }
    else if(eShipType == eShipType_Starbase)
    {
        m_wPhaserDamage     = 120;
        m_dwMaxSpeed        = 2;
        m_dwMaxFuel         = 60000;
        m_dwMaxShield       = 500;
        m_dwMaxDamage       = 600;
        m_dwMaxEngineTemp   = 1000;
        m_dwMaxWeaponTemp   = 1300;
        m_wMaxArmies        = 20;
        m_wWidth            = 20;
        m_wHeight           = 20;
        m_dwTorpSpeed       = 14;
    }
    else if(eShipType == eShipType_Att)
    {
        m_wPhaserDamage     = 10000;
        m_dwMaxSpeed        = 60;
        m_dwMaxFuel         = 12000;
        m_dwMaxShield       = 30000;
        m_dwMaxDamage       = 30000;
        m_dwMaxEngineTemp   = 10000;
        m_dwMaxWeaponTemp   = 10000;
        m_wMaxArmies        = 1000;
        m_wWidth            = 28;
        m_wHeight           = 28;
        m_dwTorpSpeed       = 30;
    }
    else if(eShipType == eShipType_SGalaxy)
    {
        m_wPhaserDamage     = 10000;
        m_dwMaxSpeed        = 60;
        m_dwMaxFuel         = 12000;
        m_dwMaxShield       = 30000;
        m_dwMaxDamage       = 30000;
        m_dwMaxEngineTemp   = 10000;
        m_dwMaxWeaponTemp   = 10000;
        m_wMaxArmies        = 1000;
        m_wWidth            = 20;
        m_wHeight           = 20;
        m_dwTorpSpeed       = 30;
    }
    else if(eShipType == eShipType_Cruiser)
    {
        m_wPhaserDamage     = 100;
        m_dwMaxSpeed        = 9;
        m_dwMaxFuel         = 10000;
        m_dwMaxShield       = 100;
        m_dwMaxDamage       = 100;
        m_dwMaxEngineTemp   = 1000;
        m_dwMaxWeaponTemp   = 1000;
        m_wMaxArmies        = 10;
        m_wWidth            = 20;
        m_wHeight           = 20;
        m_dwTorpSpeed       = 12;
    }
    else
    {
        __asm { int 0x3 }
    }
}


void CPlayerCloakHandler::PlayerAlive()
{
    Reset();
    m_pMaterialInstance = new EntityMaterialInstance(m_pPlayer->GetEntity());
    m_pMaterialInstance->setTransparency(0.0f);
}

void CPlayerCloakHandler::SetCloakState(float flCloakState)
{
    m_flTargetCloakState = flCloakState;
}

void CPlayerCloakHandler::Update(float flTimeDiff)
{
    if(m_pMaterialInstance == NULL)
    {
        return;
    }

    float flCurrentTransparency = m_pMaterialInstance->getTransparency();
    if(m_flTargetCloakState == flCurrentTransparency)
    {
        return;
    }

    float flDiff = flTimeDiff * 0.5f;

    if(m_flTargetCloakState > flCurrentTransparency)
    {
        flCurrentTransparency += flDiff;
        if(flCurrentTransparency > m_flTargetCloakState)
        {
            flCurrentTransparency = m_flTargetCloakState;
        }
    }
    else
    {
        flCurrentTransparency -= flDiff;
        if(flCurrentTransparency < m_flTargetCloakState)
        {
            flCurrentTransparency = m_flTargetCloakState;
        }

    }

    m_pMaterialInstance->setTransparency(flCurrentTransparency);
}

void CPlayerCloakHandler::Reset()
{
    if(m_pMaterialInstance != NULL)
    {
        delete m_pMaterialInstance;
        m_pMaterialInstance = NULL;
    }
    m_flTargetCloakState = 0.0f;
}

CPlayerCloakHandler::~CPlayerCloakHandler()
{
    Reset();
}

CPlayerTractorHandler::CPlayerTractorHandler(CGamePlayer* pPlayer, CGameWorld* pGameWorld) : m_pPlayer(pPlayer), m_pGameWorld(pGameWorld), m_pTractorNode(NULL), m_pTractorEntity(NULL)
{
}

CPlayerTractorHandler::~CPlayerTractorHandler()
{
    DeleteNodeAndEntity(m_pTractorNode, m_pTractorEntity);
}

void CPlayerTractorHandler::Update(float)
{
    bool isAttached = m_pTractorNode != NULL && m_pTractorNode->getParentSceneNode() != NULL;
    bool isTractoring = (m_pPlayer->GetFlags() & ePlayerFlag_Tract) || (m_pPlayer->GetFlags() & ePlayerFlag_Press);

    if(isAttached && !isTractoring)
    {
        m_pTractorNode->getParentSceneNode()->removeChild(m_pTractorNode);
        m_pPlayer->EndTractor();
    }
                     
    if(isTractoring && !isAttached)
    {
        if(m_pTractorNode == NULL)
        {
            ostringstream tractorNameStream;

            tractorNameStream << "Tractor" << m_pPlayer->GetPlayerID();
            string tractorName = tractorNameStream.str();

            m_pTractorNode = m_pGameWorld->GetSceneManager()->createSceneNode(tractorName);

            m_pTractorEntity = m_pGameWorld->GetSceneManager()->createEntity(m_pTractorNode->getName(), "UnitPyramid.mesh");
            m_pTractorEntity->setCastShadows(false);

            m_pTractorNode->attachObject(m_pTractorEntity);
        }

        if(m_pPlayer->GetFlags() & ePlayerFlag_Press)
        {
            m_pTractorEntity->setMaterialName("pressorMaterial");
        }
        else
        {
            m_pTractorEntity->setMaterialName("tractorMaterial");
        }

        m_pGameWorld->GetSceneManager()->getRootSceneNode()->addChild(m_pTractorNode);
        m_pPlayer->BeginTractor();
    }

    if(!isTractoring)
    {
        return;
    }

    CGamePlayer* pPlayerHit = m_pGameWorld->GetPlayerByID(m_dwPlayerIDTarget);
    Vector3 vectorFiring = Vector3(pPlayerHit->GetRenderedXLocation(), pPlayerHit->GetRenderedYLocation(), 0.0f) - 
                           Vector3(m_pPlayer->GetRenderedXLocation(), m_pPlayer->GetRenderedYLocation(), 0.0f);

    float flDistance = vectorFiring.length();
    vectorFiring.normalise();

    m_pTractorNode->setScale(c_uiShipRadius * 0.5f, flDistance / 2.0f, c_uiShipRadius * 0.4f);

    Vector3 vectorOffset = (vectorFiring * flDistance / 2.0f) + Vector3(m_pPlayer->GetRenderedXLocation(), m_pPlayer->GetRenderedYLocation(), 0.0f);
    m_pTractorNode->setPosition(vectorOffset.x, vectorOffset.y, 0.0f);


    float dotProduct = vectorFiring.dotProduct(Vector3(0.0f, -1.0f, 0.0f));
    float flAngle = acos(dotProduct);

    if(vectorFiring.x < 0.0f)
    {
        flAngle = (Math::TWO_PI) - flAngle;
    }

    m_pTractorNode->resetOrientation();
    m_pTractorNode->roll(Radian(flAngle));
}


